package com.imooc.diners.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.imooc.commons.constant.ApiConstant;
import com.imooc.commons.constant.RedisKeyConstant;
import com.imooc.commons.exception.ParameterException;
import com.imooc.commons.model.domain.ResultInfo;
import com.imooc.commons.model.vo.NearMeDinerVO;
import com.imooc.commons.model.vo.ShortDinerInfo;
import com.imooc.commons.model.vo.SignInDinerInfo;
import com.imooc.commons.utils.AssertUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisCommands;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @author gcq
 * @Create 2022-12-07
 */
@Service
public class NearMeService {


    @Value("${service.name.ms-oauth-server}")
    private String oauthServerName;

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private DinersService dinersService;

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 获取附近的人
     *
     * @param accessToken 用户信息
     * @param radius 距离
     * @param lng 经度
     * @param lat 维度
     * @return
     */
    public List<NearMeDinerVO> findNearMe(String accessToken,
                                          Integer radius,
                                          Float lng, Float lat){
        // 获取登录用户信息
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
        if(radius == null){
            radius = 100;
        }
        // 获取 key
        String key = RedisKeyConstant.diner_location.getKey();
        Point point = null;
        // 获取用户经纬度
        if(lng == null || lat == null){
            List<Point> points = redisTemplate.opsForGeo().position(key, dinerInfo.getId());
            AssertUtil.isTrue(points == null || points.isEmpty(), "获取经纬度失败");
            point = points.get(0);
        } else {
            point = new Point(lng, lat);
        }
        // 初始化距离对象 单位 m
        Distance distance = new Distance(radius, RedisGeoCommands.DistanceUnit.METERS);
        // 初始化 Geo 命令参数对象
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs();
        // 附近的热限制20 包含距离，由近到远
        args.limit(20).includeDistance().sortAscending();
        // 以用户经纬度为中心，范围1000m
        Circle circle = new Circle(point, distance);
        // 获取附近的人 GeoLocation信息
        GeoResults<RedisGeoCommands.GeoLocation> geoResult = redisTemplate.opsForGeo().radius(key, circle, args);
        Map<Integer, NearMeDinerVO> nearMeDinerVOMap = Maps.newLinkedHashMap();
        geoResult.forEach(result -> {
            RedisGeoCommands.GeoLocation<Integer> geoLocation = result.getContent();
            // 初始化 VO 对象
            NearMeDinerVO nearMeDinerVO = new NearMeDinerVO();
            nearMeDinerVO.setId(geoLocation.getName());
            // 获取距离
            double dist = result.getDistance().getValue();
            String distanceStr = NumberUtil.round(dist, 1).toString();
            nearMeDinerVO.setDistance(distanceStr);
            nearMeDinerVOMap.put(geoLocation.getName(), nearMeDinerVO);
        });
        // 获取附近的人信息
        Integer[] dinerIds = nearMeDinerVOMap.keySet().toArray(new Integer[]{});
        List<ShortDinerInfo> shorDinerInfos = dinersService.findByIds(StrUtil.join(",", dinerIds));
        // 完善用户头像昵称信息
        shorDinerInfos.forEach(shortDinerInfo -> {
            NearMeDinerVO nearMeDinerVO = nearMeDinerVOMap.get(shortDinerInfo.getId());
            nearMeDinerVO.setAvatarUrl(shortDinerInfo.getAvatarUrl());
            nearMeDinerVO.setNickname(shortDinerInfo.getNickname());
        });
        return Lists.newArrayList(nearMeDinerVOMap.values());
    }

    /**
     * 更新食客坐标
     *
     * @param accessToken 登录用户 Token
     * @param lng         经度
     * @param lat         维度
     */
    public void updateDinerLocation(String accessToken, Float lng, Float lat) {
        AssertUtil.isTrue(lng == null, "获取经度失败");
        AssertUtil.isTrue(lat == null, "获取维度失败");
        // 获取登录用户信息
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
        // 获取 key
        String key = RedisKeyConstant.diner_location.getKey();
        // 将用户信息存入 Redis
        RedisGeoCommands.GeoLocation geoLocation = new RedisGeoCommands.GeoLocation(dinerInfo.getId(), new Point(lng, lat));

        redisTemplate.opsForGeo().add(key, geoLocation);
    }

    /**
     * 获取登录用户信息
     *
     * @param accessToken 访问token
     * @return 登录用户信息
     */
    private SignInDinerInfo loadSignInDinerInfo(String accessToken) {
        AssertUtil.mustLogin(accessToken);

        String url = oauthServerName + "/user/me?access_token={accessToken}";
        ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);
        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(resultInfo.getMessage());
        }
        SignInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(), new SignInDinerInfo(), false);
        return dinerInfo;
    }
}